package ai;

import java.util.Random;

import com.jme.math.Vector3f;

import ai.attackControllers.AttackController;
import ai.attackControllers.EasyAttackController;
import ai.attackControllers.HardAttackController;
import ai.attackControllers.MediumAttackController;
import ai.equipmentManagers.EasyEquipmentManager;
import ai.equipmentManagers.EquipmentManager;
import ai.equipmentManagers.HardEquipmentManager;
import ai.equipmentManagers.MediumEquipmentManager;
import ai.evadeControllers.EasyEvadeController;
import ai.evadeControllers.EvadeController;
import ai.evadeControllers.HardEvadeController;
import ai.evadeControllers.MediumEvadeController;

import settings.Config;
import logic.DelayedTPController;
import logic.common.player.Player;
import logic.items.ItemType;
import logic.items.RepairItemProperties;
import logic.items.ShieldItemProperties;
import logic.nodes.TeamNode;
import logic.ships.hunter.Hunter;
import logic.ships.mothership.MotherShip;
import logic.weapons.WeaponType;
import map.spawnStation.SpawnStation;

/**
 * Controls all actions of an AI-pilot.
 * 
 * @author Wasserleiche
 */
public class PilotController extends DelayedTPController {
	
	private static final long serialVersionUID = 1L;
	
	private float MIN_TARGET_DIST, MAX_TARGET_DIST, MAX_EVADE_TIME;
	private float MAX_BEING_CHASED_TIME, MAX_NO_SIGHT_TIME, MIN_SHIELDS;
	
	private EvadeController evadeController;
	private AttackController attackController;
	private CaptureController captureController;
	private EquipmentManager equipmentManager;
	/** This is the current Controller that is attached to the {@link Hunter} */
	private AbstractPilotController currentController;
	
	private Random r;
	private Player bot;
	private boolean wasStopping;
	
	private Hunter hunter, chaser;
	
	private float currentChaseTime, currentBeingChasedTime, currentNoSightTime;
	
	/**
	 * Constructs a new ChaseController.
	 * @param bot The {@link Player} to be controlled.
	 * @param scene The root-Node.
	 */
	public PilotController(Player bot, AILevel aiLevel) {
		super(2f);
		this.bot = bot;
				
		hunter = bot.getHunter();
		
		r = new Random();
		
		MIN_SHIELDS = hunter.getMaxShield() / 4f * 3f;
		
		MIN_TARGET_DIST = r.nextInt(10) + 10f;
		MAX_TARGET_DIST = r.nextInt(50) + 200f;
		MAX_EVADE_TIME = r.nextInt(10) + 5f;
		MAX_BEING_CHASED_TIME = r.nextInt(10) + 5f;
		MAX_NO_SIGHT_TIME = r.nextInt(2) + 2f;
		
		attackController = getAttackController(aiLevel);
		evadeController = getEvadeController(aiLevel);
		captureController = new CaptureController(bot);
		
		equipmentManager = getEquipmentManager(aiLevel);
		
		setController(attackController);
		
		hunter.createMoveForwardController();
	}
	
	protected AttackController getAttackController(AILevel aiLevel) {
		switch(aiLevel) {
			case Easy: return new EasyAttackController(bot);
			case Normal: return new MediumAttackController(bot);
			case Hard: return new HardAttackController(bot);
		}
		return null;
	}
	
	protected EvadeController getEvadeController(AILevel aiLevel) {
		switch(aiLevel) {
			case Easy: return new EasyEvadeController(bot);
			case Normal: return new MediumEvadeController(bot);
			case Hard: return new HardEvadeController(bot);
		}
		return null;
	}
	
	protected EquipmentManager getEquipmentManager(AILevel aiLevel) {
		switch(aiLevel) {
			case Easy: return new EasyEquipmentManager(bot);
			case Normal: return new MediumEquipmentManager(bot);
			case Hard: return new HardEquipmentManager(bot);
		}
		return null;
	}
	
	@Override
	public void delayedUpdate(float time) {
		if(!checkAIEnabled()) return;
		
		useShieldBattery();
		useRepairKit();
		
		if(!bot.isAlive() || bot.getNearSpawnPosition() != null || isInMSActionRadius()) 
			equipmentManager.manageEquipment();
		
		TeamNode target = bot.getBotTarget();
		if(!bot.isAlive() || (target != null && target.getBound() == null)) return;
		
		if(target instanceof SpawnStation) {
			setController(captureController);
			return;
		}
		
		float distance = getTargetDistance();
		
		boolean tooNear = distance < MIN_TARGET_DIST;
		boolean tooFar = distance > MAX_TARGET_DIST;
		boolean shieldsDown = hunter.getCurrentShield() < MIN_SHIELDS;
		boolean evadeTooLong = isEvading() && evadeController.getTargetTime() > MAX_EVADE_TIME;
		
		boolean targetNotVisible = !AITools.isLookingTo(hunter, target);
		boolean targetNotVisibleTooLong = targetNotVisible && currentNoSightTime > MAX_NO_SIGHT_TIME;
		
		boolean canChase = canChase(hunter, target);
		
		chaser = getChaser(hunter);
		boolean isChased = chaser != null;
		boolean beingChasedTooLong = isChased && currentBeingChasedTime > MAX_BEING_CHASED_TIME;
		
		if(canChase) currentChaseTime += time;
		else currentChaseTime = 0f;
		if(isChased) currentBeingChasedTime += time;
		else currentBeingChasedTime = 0f;
		if(targetNotVisible) currentNoSightTime += time;
		else currentNoSightTime = 0f;
		
		if(!tooFar && (tooNear || shieldsDown) && !beingChasedTooLong 
		&& !evadeTooLong && !targetNotVisibleTooLong && !canChase) 
			setController(evadeController);
		else {
			if(chaser != null && target != chaser) setCurrentTarget(chaser);
			setController(attackController);
		}
	}
	
	protected boolean isInMSActionRadius() {
		Vector3f hunterLoc = bot.getHunter().getLocalTranslation();
		MotherShip ms = bot.getTeam().getMotherShip();
		if(ms == null) return true;
		float dist = ms.getLocalTranslation().distance(hunterLoc);
		return dist <= ms.getActionRadius();
	}
	
	protected void setCurrentTarget(final TeamNode target) {
		bot.getTeam().getIngameState().getThreadPool().addPostRunnable(new Runnable() {
			@Override
			public void run() {
				bot.changeBotTarget(target);
			}
		});
	}
	
	protected void useRepairKit() {
		Hunter hunter = bot.getHunter();
		RepairItemProperties props = (RepairItemProperties)bot.getTeam().getItemProp(ItemType.Repair);
		
		if(hunter.isDamaged() && hunter.getItems(ItemType.Repair) != null 
		&& !hunter.getItems(ItemType.Repair).isEmpty() 
		&& props.getRepairAmount() <= hunter.getMaxHP() - hunter.getCurrentHP()) 
			hunter.useItem(ItemType.Repair);
	}
	
	protected void useShieldBattery() {
		Hunter hunter = bot.getHunter();
		ShieldItemProperties props = (ShieldItemProperties)bot.getTeam().getItemProp(ItemType.Shields);
		
		if(hunter.getCurrentShield() < hunter.getMaxShield() && hunter.getItems(ItemType.Shields) != null 
		&& !hunter.getItems(ItemType.Shields).isEmpty() 
		&& props.getShieldRestoreAmount() <= hunter.getMaxShield() - hunter.getCurrentShield()) 
			hunter.useItem(ItemType.Shields);
	}
	
	protected boolean checkAIEnabled() {
		if(!Config.get().getEnableAI()) {
			wasStopping = hunter.getMoveForwardController().isStopping();
			hunter.getMoveForwardController().stop(true);
			return false;
		}
		if(hunter != null) hunter.getMoveForwardController().stop(wasStopping);
		return true;
	}
	
	protected void manageEquipment() {		
//		if(hunter.getCurrentSpawnStation() == null 
//		&& !bot.getTeam().getMotherShip().isInSpawnRange(hunter.getLocalTranslation())) return;
//		
//		int hsToBuy = hunter.getMaxHeatseekerProjectiles() - hunter.getHeatseekerProjectiles();
//		fillWeapons(hunter.getHeatseakers(), hsToBuy, ItemSelectContent.HEATSEEKER_COSTS);
//		
//		int missilesToBuy = hunter.getMaxMissileProjectiles() - hunter.getMissileProjectiles();
//		fillWeapons(hunter.getMissiles(), missilesToBuy, ItemSelectContent.MISSILE_COSTS);
//		
//		buyItems(repairItemProps);
//		buyItems(shieldItemProps);
//		
//		int emptyEnergySlots = hunter.getEnergyWeaponSlots().size() - hunter.getEnergyWeapons().size();
//		if(emptyEnergySlots > 0) buyWeapons(emptyEnergySlots, bot.getTeam().getEnergyWeaponNames());
//		
//		int emptyHSSlots = hunter.getHeatseakerSlots().size() - hunter.getHeatseakers().size();
//		if(emptyHSSlots > 0) buyWeapons(emptyHSSlots, bot.getTeam().getHeatseakerNames());
//		
//		int emptyMissileSlots = hunter.getMissileSlots().size() - hunter.getMissiles().size();
//		if(emptyMissileSlots > 0) buyWeapons(emptyMissileSlots, bot.getTeam().getMissileNames());
	}
	
	protected void fillWeapons(WeaponType type, int maxProjectiles, int costs) {
		for(int i = 0; i < maxProjectiles; i++) {
			if(bot.getMoney() >= costs) {
				hunter.restockProjectiles(type, 1);
				bot.spendMoney(costs);
			} else break;
		}
	}
	
	/**
	 * Checks if the given pilot can chase the given target. That's the case if the target is 
	 * a {@link Hunter}, the target is visible and the {@link Player} is not visible to the target.
	 * @param chaser The {@link Hunter} that has to chase.
	 * @param target The object to be chased. 
	 * @return true, if the given target can be chased by the {@link Hunter}. false, else.
	 */
	protected boolean canChase(Hunter chaser, TeamNode target) {
		if(target instanceof Hunter) {
			Hunter targetHunter = (Hunter)target;
			return AITools.isVisible(chaser, targetHunter) && !AITools.isVisible(targetHunter, chaser);
		}
		return false;
	}
	
	/**
	 * Checks if the given {@link Hunter} is chased by another pilot. This is the case if there is an enemy 
	 * {@link Player} whose target is the given {@link Hunter} and if this {@link Player} is able to chase 
	 * the given {@link Hunter}.
	 * @param hunter The {@link Hunter} that has to be checked for being chased.
	 * @return The {@link Hunter} chasing the given {@link Hunter}. null, if there is no such {@link Hunter}.
	 */
	protected Hunter getChaser(Hunter hunter) {
		for(Player player : bot.getEnemyTeam().getAllPlayers()) {
			if(player.getHunter() == null || player.getHunter().isDestroyed() 
			|| player.getBotTarget() != hunter) continue;
			if(canChase(player.getHunter(), hunter)) return player.getHunter();
		}
		return null;
	}
	
	/**
	 * Removes the currentController from the {@link Hunter} and replaces it with the given Controller.
	 * @param controller The {@link AbstractPilotController} that has to replace the currentController.
	 */
	private void setController(AbstractPilotController controller) {
		if(currentController == controller) return;
		if(currentController != null) hunter.removeController(currentController);
		
		currentController = controller;
		currentController.resetTargetTime();
		hunter.addController(currentController);
	}
	
	private float getTargetDistance() {
		TeamNode target = bot.getBotTarget();
		if(target == null) return 0f;
		return hunter.getLocalTranslation().distance(target.getLocalTranslation()) - target.getSize();
	}
	
	/**
	 * Checks if the pilot is currently evading.
	 * @return true, if the pilot is evading. false, else.
	 */
	public boolean isEvading() { return currentController == evadeController; }
	
	/**
	 * Checks if the pilot is currently attacking.
	 * @return true, if the pilot is attacking. false, else.
	 */
	public boolean isAttacking() { return currentController == attackController; }
	
	/**
	 * Checks if the pilot is currently capturing a {@link SpawnStation}.
	 * @return true, if the pilot is capturing. false, else.
	 */
	public boolean isCapturing() { return currentController == captureController; }
	
	public float getChaseTime() { return currentChaseTime; }
}